package utils

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"github.com/tjfoc/gmsm/sm3"
	"io/ioutil"
	"log"
	"os"
	"private-go-sdk/commons"
	"private-go-sdk/config"
	"private-go-sdk/constant"
	"private-go-sdk/http"
	"reflect"
	"strconv"
	"strings"
)

type SdkClient struct {
	ServerUrl        string
	AccessToken      string
	AccessSecret     string
	SignatureVersion string
	TraceId          string
	IdempotentToken  string
	EnableNonce      bool
	CustomHeaders    map[string]string
	ProxyInfo        *config.ProxyInfo
}

func NewSdkClient(serverUrl string, accessToken string, accessSecret string) *SdkClient {
	sdkClient := SdkClient{
		ServerUrl:        serverUrl,
		AccessToken:      accessToken,
		AccessSecret:     accessSecret,
		SignatureVersion: commons.MD5,
		EnableNonce:      true,
	}
	return &sdkClient
}

// 返回字符串
func (sdk *SdkClient) ServiceAsString(request SdkRequest) string {
	return sdk.SdkRequest(request)
}

// 只返回code、message
func (sdk *SdkClient) Service(request SdkRequest) *SdkResponse {
	jsonStr := sdk.SdkRequest(request)
	response := SdkResponse{}
	var jsonObj map[string]interface{}
	if err := json.Unmarshal([]byte(jsonStr), &jsonObj); err != nil {
		log.Println("Error parsing JSON:", err)
		return &response
	}
	codeStr := fmt.Sprintf("%v", jsonObj["code"])
	code, _ := strconv.ParseInt(codeStr, 10, 64)
	response.Code = code
	response.Message = jsonObj["message"].(string)
	response.ResultStream = false
	return &response
}

// code、message、result（包括基本类型和对象类型和对象数组类型）
func (sdk *SdkClient) ServiceAsModel(request SdkRequest, obj interface{}) *SdkResponse {
	jsonStr := sdk.SdkRequest(request)
	response := SdkResponse{}
	var jsonObj map[string]interface{}
	if err := json.Unmarshal([]byte(jsonStr), &jsonObj); err != nil {
		log.Println("Error parsing JSON:", err)
		return &response
	}
	codeStr := fmt.Sprintf("%v", jsonObj["code"])
	code, _ := strconv.ParseInt(codeStr, 10, 64)
	response.Code = code
	response.Message = jsonObj["message"].(string)
	response.ResultStream = false
	delete(jsonObj, "code")
	delete(jsonObj, "message")
	num := len(jsonObj)
	// 返回结果只存在code、message
	if num == 0 {
		return &response
	}

	//处理返回值为code、message、result（基础类型）
	basicType := isBasicType(obj)
	if basicType {
		// 检查是否存在某个键
		value, exists := constant.BASICS_TYPE[request.GetUrl()]
		if exists {
			result, _ := jsonObj[value]
			response.Result = result
		} else {
			for _, result := range jsonObj {
				response.Result = result
			}
		}
		return &response
	}

	//处理返回值为code、message、result（对象类型）
	// 返回结果除去code、message之后，只有一个字段，可能出现的结果如下:
	// 1、基本类型
	// 2、对象类型
	var resultData interface{}
	if num == 1 {
		for key, value := range jsonObj {
			valueType := reflect.ValueOf(value).Kind()
			if valueType == reflect.Map {
				resultData = value
			} else {
				flag := commons.IsPropertyExists(obj, key)
				if flag {
					resultData = jsonObj
				}
			}
			jsonBytes, _ := json.Marshal(resultData)
			json.Unmarshal(jsonBytes, obj)
			response.Result = obj
			return &response
		}
	}

	//处理返回值为code、message、result1（任意类型）、result2（任意类型）【将result1、result2封装为一个对象中的两个字段】
	if num > 1 {
		fieldName, exists := constant.EXTRA_FIELD[request.GetUrl()]
		if exists {
			result, _ := jsonObj[fieldName]
			switch reflect.TypeOf(result).Kind() {
			case reflect.Map:
				if resultMap, ok := result.(map[string]interface{}); ok {
					for key, value := range jsonObj {
						if fieldName != key {
							resultMap[key] = value
						}
					}
					resultData = resultMap
				}
			case reflect.Slice:
				if resultList, ok := result.([]interface{}); ok {
					for _, value := range resultList {
						if resultMap, ok := value.(map[string]interface{}); ok {
							for key, value := range jsonObj {
								if fieldName != key {
									resultMap[key] = value
								}
							}
						}
					}
					resultData = result
				}
			}
		} else {
			resultData = jsonObj
		}
	}
	jsonBytes, _ := json.Marshal(resultData)
	json.Unmarshal(jsonBytes, obj)
	response.Result = obj
	return &response
}

// 文件下载
func (sdk *SdkClient) Download(request SdkRequest, file *os.File) *SdkResponse {
	if strings.HasSuffix(sdk.ServerUrl, "/") {
		sdk.ServerUrl = sdk.ServerUrl[:len(sdk.ServerUrl)-1]
	}
	url := sdk.ServerUrl + request.GetUrl()
	parameter := request.GetHttpParameter()
	header := sdk.buildHttpHeader(parameter)
	jsonStr := ""
	if parameter.IsJson() {
		jsonStr = http.DoDownloadWithJson(url, parameter, header, file, sdk.ProxyInfo)
	} else {
		jsonStr = http.DoDownload(url, parameter, header, file, sdk.ProxyInfo)
	}
	response := SdkResponse{}
	if len(jsonStr) <= 0 {
		response.Code = 0
		response.Message = "SUCCESS"
		response.ResultStream = true
		return &response
	}
	response.ResultStream = false
	var jsonObj map[string]interface{}
	if err := json.Unmarshal([]byte(jsonStr), &jsonObj); err != nil {
		log.Println("Error parsing JSON:", err)
		return &response
	}
	codeStr := fmt.Sprintf("%v", jsonObj["code"])
	code, _ := strconv.ParseInt(codeStr, 10, 64)
	response.Code = code
	response.Message = jsonObj["message"].(string)
	delete(jsonObj, "code")
	delete(jsonObj, "message")
	num := len(jsonObj)
	// 返回结果只存在code、message
	if num == 0 {
		return &response
	}
	var resultObj interface{}
	for _, result := range jsonObj {
		resultObj = result
	}
	resultData, _ := json.Marshal(resultObj)
	reflectObj, exists := constant.STREAM_RESULT[request.GetUrl()]
	if exists {
		personValue := reflect.New(reflectObj).Elem()
		err := json.Unmarshal(resultData, personValue.Addr().Interface())
		if err != nil {
			fmt.Println("Error unmarshalling JSON:", err)
			return &response
		}
		obj := personValue.Interface()
		response.Result = obj
		return &response
	}
	return &response
}

func (sdk *SdkClient) SdkRequest(request SdkRequest) string {
	if strings.HasSuffix(sdk.ServerUrl, "/") {
		sdk.ServerUrl = sdk.ServerUrl[:len(sdk.ServerUrl)-1]
	}
	url := sdk.ServerUrl + request.GetUrl()
	parameter := request.GetHttpParameter()
	header := sdk.buildHttpHeader(parameter)
	if parameter.IsJson() {
		return http.DoServiceWithJson(url, parameter, header, sdk.ProxyInfo)
	}
	return http.DoService(url, parameter, header, sdk.ProxyInfo)
}

func (sdk *SdkClient) buildHttpHeader(httpParameter *http.HttpParameter) *http.HttpHeader {
	timestamp := commons.GetTimeStamp()
	nonce := ""
	if sdk.EnableNonce {
		nonce = commons.GetUUID()
	}
	var signature string
	if commons.HMACSHA256 == sdk.SignatureVersion {
		hmacSignatureList := make([]ParameterBody, 0)
		hmacSignatureList = append(hmacSignatureList, NewStringParameter(commons.ACCESS_TOKEN, sdk.AccessToken))
		hmacSignatureList = append(hmacSignatureList, NewStringParameter(commons.TIMESTAMP, timestamp))
		hmacSignatureList = append(hmacSignatureList, NewStringParameter(commons.NONCE, nonce))
		hmacSignatureList = append(hmacSignatureList, NewStringParameter(commons.SIGNATURE_VERSION, sdk.SignatureVersion))
		hmacSignatureList = append(hmacSignatureList, NewStringParameter(commons.IDEMPOTENTTOKEN, sdk.IdempotentToken))
		hmacSignatureList = append(hmacSignatureList, NewStringParameter(commons.TRACEID, sdk.TraceId))
		if httpParameter.IsJson() {
			hmacSignatureList = append(hmacSignatureList, NewStringParameter(commons.BODY, httpParameter.JsonParamer()))
		} else {
			hmacSignatureList = append(hmacSignatureList, NewStringParameter(commons.BODY, ""))
			params := httpParameter.Params()
			for key, value := range params {
				if commons.IsNil(value) {
					continue
				}
				switch t := value.(type) {
				case string:
					if len(t) != 0 {
						hmacSignatureList = append(hmacSignatureList, NewStringParameter(key, fmt.Sprintf("%v", t)))
					}
				case *int, *int32, *int64:
					if f, ok := value.(*int64); ok {
						hmacSignatureList = append(hmacSignatureList, NewStringParameter(key, fmt.Sprintf("%d", *f)))
					}
				case *float32, *float64:
					if f, ok := value.(*float64); ok {
						hmacSignatureList = append(hmacSignatureList, NewStringParameter(key, fmt.Sprintf("%f", *f)))
					}
				case *bool:
					if f, ok := value.(*bool); ok {
						hmacSignatureList = append(hmacSignatureList, NewStringParameter(key, fmt.Sprintf("%t", *f)))
					}
				default:
					hmacSignatureList = append(hmacSignatureList, NewStringParameter(key, fmt.Sprintf("%v", value)))
				}
			}
			files := httpParameter.Files()
			for key, file := range files {
				defer file.Close()
				bytes, _ := ioutil.ReadAll(file)
				hmacSignatureList = append(hmacSignatureList, NewBytesParameter(key, bytes))
			}
			listFiles := httpParameter.ListFiles()
			for key, fileList := range listFiles {
				for _, file := range fileList {
					defer file.Close()
					bytes, _ := ioutil.ReadAll(file)
					hmacSignatureList = append(hmacSignatureList, NewBytesParameter(key, bytes))
				}
			}
		}
		signature = sdk.CalculateSignature(httpParameter.HttpMethod(), hmacSignatureList)
	} else if commons.SM3 == sdk.SignatureVersion {
		rawSig := sdk.AccessToken + sdk.AccessSecret + timestamp + nonce
		hash := sm3.New()
		hash.Write([]byte(rawSig))
		result := hash.Sum(nil)
		signature = hex.EncodeToString(result)
	} else {
		signature = commons.GetMD5(sdk.AccessToken + sdk.AccessSecret + timestamp + nonce)
	}
	header := http.HttpHeader{
		AccessToken:      sdk.AccessToken,
		Timestamp:        timestamp,
		Nonce:            nonce,
		Signature:        signature,
		SignatureVersion: sdk.SignatureVersion,
		TraceId:          sdk.TraceId,
		IdempotentToken:  sdk.IdempotentToken,
		CustomHeaders:    sdk.CustomHeaders,
	}
	return &header
}

func (sdk *SdkClient) CalculateSignature(method string, hmacSignatureList []ParameterBody) string {
	SortParameterBody(hmacSignatureList)
	mac := hmac.New(sha256.New, []byte(sdk.AccessSecret))
	mac.Write([]byte(method))
	for _, parameterBody := range hmacSignatureList {
		str := commons.CONNECTOR + parameterBody.Name + commons.EQUALSIGN
		mac.Write([]byte(str))
		if parameterBody.Bytes != nil {
			mac.Write(parameterBody.Bytes)
		}
	}
	sum := mac.Sum(nil)
	hexMac := make([]byte, len(sum)*2)
	hex.Encode(hexMac, sum)
	return fmt.Sprintf("%s", hexMac)
}
